import { Entity, Point } from "../entity"
import { Search } from "./search"
import { WhereField } from "./where-field"

type Units = 'm' | 'km' | 'ft' | 'mi'

/** A function that defines a circle for `.inCircle` searches. */
export type CircleFunction = (circle: Circle) => Circle

/** A builder that defines a circle. */
export class Circle {

  /** @internal */
  longitudeOfOrigin: number = 0

  /** @internal */
  latitudeOfOrigin: number = 0

  /** @internal */
  size: number = 1

  /** @internal */
  units: Units = 'm'

  /**
   * Sets the longitude. If not set, defaults to 0.0.
   *
   * @param value The longitude.
   * @returns This instance.
   */
  longitude(value: number) {
    this.longitudeOfOrigin = value
    return this
  }

  /**
   * Sets the latitude. If not set, defaults to 0.0.
   *
   * @param value The latitude.
   * @returns This instance.
   */
  latitude(value: number) {
    this.latitudeOfOrigin = value
    return this
  }

  /**
   * Sets the origin of the circle using a {@link Point}. If not
   * set, defaults to [Null Island](https://en.wikipedia.org/wiki/Null_Island).
   *
   * @param point A {@link Point} containing the longitude and latitude of the origin.
   * @returns This instance.
   */
  origin(point: Point): Circle

  /**
   * Sets the origin of the circle. If not set, defaults to
   * [Null Island](https://en.wikipedia.org/wiki/Null_Island).
   *
   * @param longitude The longitude.
   * @param latitude The latitude.
   * @returns This instance.
   */
  origin(longitude: number, latitude: number): Circle

  /** @internal */
  origin(pointOrLongitude: number | Point, latitude?: number): Circle {
    if (typeof pointOrLongitude === 'number' && latitude !== undefined) {
      this.longitudeOfOrigin = pointOrLongitude
      this.latitudeOfOrigin = latitude
    } else {
      const point = pointOrLongitude as Point
      this.longitudeOfOrigin = point.longitude
      this.latitudeOfOrigin = point.latitude
    }
    return this
  }

  /**
   * Sets the radius of the {@link Circle}. Defaults to 1. If units are
   * not specified, defaults to meters.
   *
   * @param size The radius of the circle.
   * @returns This instance.
   */
  radius(size: number) {
    this.size = size
    return this
  }

  /**
   * Sets the units to meters.
   * @returns This instance.
   */
  get m() { return this.meters }

  /**
   * Sets the units to meters.
   * @returns This instance.
   */
  get meter() { return this.meters }

  /**
   * Sets the units to meters.
   * @returns This instance.
   */
  get meters() {
    this.units = 'm'
    return this
  }

  /**
   * Sets the units to kilometers.
   * @returns This instance.
   */
  get km() { return this.kilometers }

  /**
   * Sets the units to kilometers.
   * @returns This instance.
   */
  get kilometer() { return this.kilometers }

  /**
   * Sets the units to kilometers.
   * @returns This instance.
   */
  get kilometers() {
    this.units = 'km'
    return this
  }

  /**
   * Sets the units to feet.
   * @returns This instance.
   */
  get ft() { return this.feet }

  /**
   * Sets the units to feet.
   * @returns This instance.
   */
  get foot() { return this.feet }

  /**
   * Sets the units to feet.
   * @returns This instance.
   */
  get feet() {
    this.units = 'ft'
    return this
  }

  /**
   * Sets the units to miles.
   * @returns This instance.
   */
  get mi() { return this.miles }

  /**
   * Sets the units to miles.
   * @returns This instance.
   */
  get mile() { return this.miles }

  /**
   * Sets the units to miles.
   * @returns This instance.
   */
  get miles() {
    this.units = 'mi'
    return this
  }
}

export class WherePoint<T extends Entity> extends WhereField<T> {
  private circle: Circle = new Circle()

  inRadius(circleFn: CircleFunction): Search<T> {
    return this.inCircle(circleFn)
  }

  inCircle(circleFn: CircleFunction): Search<T> {
    this.circle = circleFn(this.circle)
    return this.search
  }

  toString(): string {
    const { longitudeOfOrigin, latitudeOfOrigin, size, units } = this.circle
    return this.buildQuery(`[${longitudeOfOrigin} ${latitudeOfOrigin} ${size} ${units}]`)
  }
}
